package com.example.mq.mqserver;

import com.example.mq.common.Consumer;
import com.example.mq.common.MqException;
import com.example.mq.mqserver.core.*;
import com.example.mq.mqserver.datacenter.DiskDataManage;
import com.example.mq.mqserver.datacenter.MemoryDataManage;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.Comparator;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 通过这个类表示虚拟主机
 * 每个虚拟主机下都管理着自己的交换机，对列，消息，绑定
 * 对上层提供api调用，同时作为逻辑的整合类，在这里需要对异常进行处理，
 */
@Slf4j
public class VirtualHost {
    private String VirtualHostName;
    @Getter
    private DiskDataManage diskDataManage = new DiskDataManage();
    @Getter
    private MemoryDataManage memoryDataManage = new MemoryDataManage();

    private ConsumerManager consumerManager = new ConsumerManager(this);
    //定义操作队列的锁对象
    private final Object queueLocker = new Object();
    //定义操作交换机的锁对象
    private final Object exchangeLocker = new Object();
    public VirtualHost(String virtualName) {
        this.VirtualHostName = virtualName;
        //对于MemoryDataManage来说不需要进行初始化，直接拿来用就行
        //而对于DiskDataManage来说，此时涉及到数据库操作，此时就需要进行建库建表
        diskDataManage.init();

        //另外还需要针对硬盘中的数据恢复到内存中
        try {
            memoryDataManage.recovery(diskDataManage);
            log.info("[VirtualHost] 数据恢复成功");
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            log.warn("[VirtualHost] 数据恢复失败");
        }
    }
    public String getVirtualHostName() {
        return this.VirtualHostName;
    }
    //创建交换机(这里的参数是依据RabbitMQ中的样子来的)
    public boolean exchangeDeclare(String exchangeName, ExchangeType type, boolean durable, boolean autoDelete,
                                Map<String,Object> arguments) {

        //区分不同虚拟主机上的交换机
        exchangeName = VirtualHostName + exchangeName;
        synchronized (exchangeLocker) {
            try {
                //1.判断对应交换机(内存上)是否存在
                Exchange toAdd = memoryDataManage.getExchange(exchangeName);
                if (toAdd != null) {
                    throw new MqException("[VirtualHost] 当前需要创建的交换机已经存在");
                }
                //2.如果不存在就进行创建
                Exchange newExchange = new Exchange();
                newExchange.setArguments(arguments);
                newExchange.setType(type);
                newExchange.setDurable(durable);
                newExchange.setName(exchangeName);
                newExchange.setAutoDelete(autoDelete);
                //3.写入硬盘(持久化)
                if (durable) {
                    diskDataManage.insertExchange(newExchange);
                }
                //4.写入内存
                memoryDataManage.insertExchange(newExchange);
                log.info("[VirtualHost] 交换机创建成功");
                return true;
            }catch (Exception e) {
                //一旦上述过程发生异常，就说明交换机创建失败
                log.info("[VirtualHost] 交换机创建失败");
                e.printStackTrace();
                return false;
            }
        }
    }

    //删除交换机
    public boolean exchangeDelete(String exchangeName) {
        exchangeName = VirtualHostName + exchangeName;
        try {
            synchronized (exchangeLocker) {
                //1.首先判断对应交换机是否存在
                Exchange toDelete = memoryDataManage.getExchange(exchangeName);
                if (toDelete == null) {
                    throw new MqException("[VirtualHost] 当前交换机不存在");
                }
                //2.进行硬盘上的删除
                if(toDelete.isDurable()) {
                    diskDataManage.deleteExchange(exchangeName);
                }
                //3.进行内存删除
                memoryDataManage.deleteExchange(exchangeName);

                log.info("[VirtualHost] 删除交换机成功");
                return true;
            }
        }catch (Exception e) {
            e.printStackTrace();
            log.info("[VirtualHost] 删除交换机失败");
            return false;
        }
    }
    //创建队列
    public boolean queueDeclare(String queueName,boolean durable,boolean autoDelete,boolean exclusive,
                                Map<String,Object> argument) {
        queueName = VirtualHostName + queueName;
        try {
            synchronized (queueLocker) {
                //首先创建queue相关文件
                diskDataManage.createQueueFiles(queueName);
                //1.判断当前队列是否存在
                MSGQueue queue = memoryDataManage.getQueue(queueName);
                if (queue != null) {
                    throw new MqException("[VirtualHost] 当前队列已经存在");
                }
                //2.创建新的队列
                MSGQueue toAdd = new MSGQueue();
                toAdd.setExclusive(exclusive);
                toAdd.setArguments(argument);
                toAdd.setName(queueName);
                toAdd.setDurable(autoDelete);
                toAdd.setDurable(durable);
                //3.存储在硬盘中
                if (durable) {
                    diskDataManage.insertQueue(toAdd);
                }
                //4.存储在内存中
                memoryDataManage.insertQueue(toAdd);
                log.info("[VirtualHost] 创建队列成功");
                return true;
            }
        }catch (Exception e) {
            //如果出现异常，则说明创建队列失败
            e.printStackTrace();
            log.info("[VirtualHost] 创建队列失败");
            return false;
        }
    }
    //删除队列
    public boolean queueDelete(String queueName) {
        //首先对queueName进行处理
        queueName = VirtualHostName + queueName;
        try {
            synchronized (queueLocker) {
                //1.检查队列是否存在
                MSGQueue queue = memoryDataManage.getQueue(queueName);
                if (queue == null) {
                    throw new MqException("[VirtualHost] 当前队列不存在");
                }
                //2.进行判断当前队列是否持久化，如果持久化才进行硬盘删除
                if (queue.isDurable()) {
                    diskDataManage.deleteQueue(queueName);
                }
                //3.删除内存中的存储的数据
                memoryDataManage.deleteQueue(queueName);
                //4.删除queue目录文件
                diskDataManage.deleteQueueFiles(queueName);
            }
            log.info("[VirtualHost] 队列删除成功");
            return true;

        }catch (Exception e) {
            e.printStackTrace();
            log.info("[VirtualHost] 队列删除失败");
            return false;
        }
    }

    //创建绑定(必须要对应的交换机和队列同时存在此时绑定才有存储的意义)
    public boolean queueBind(String queueName, String exchangeName, String bindingKey) {
        queueName = VirtualHostName + queueName;
        exchangeName = VirtualHostName + exchangeName;
        try {
            synchronized(exchangeLocker) {
                synchronized (queueLocker) {
                    //1.先根据queueName和exchangeName查看绑定是否存在
                    Binding binding = memoryDataManage.getBinding(queueName,exchangeName);
                    if(binding != null) {
                        throw new MqException("[VirtualHost] 当前绑定已存在");
                    }
                    //2.检查一下bindingKey是否合法
                    if(!Router.checkBindingKey(bindingKey)) {
                        throw new MqException("[VirtualHost] bindingKey不合法，创建绑定失败");
                    }
                    //3.检查对应的交换机和队列是否存在
                    MSGQueue queue = memoryDataManage.getQueue(queueName);
                    Exchange exchange = memoryDataManage.getExchange(exchangeName);
                    if(queue == null || exchange == null) {
                        throw new MqException("[VirtualHost] 交换机或队列不存在,创建队列失败");
                    }
                    //4.构造binding对象
                    Binding newBinding = new Binding();
                    newBinding.setBindingKey(bindingKey);
                    newBinding.setQueueName(queueName);
                    newBinding.setExchangeName(exchangeName);
                    //5.看队列和交换机是否持久化
                    if(queue.isDurable() && exchange.isDurable()) {
                        diskDataManage.insertBinding(newBinding);
                    }
                    //6.写入内存
                    memoryDataManage.insertBinding(newBinding);
                    log.info("[VirtualHost] 创建绑定成功");
                }
            }
            return true;
        }catch (Exception e) {
            e.printStackTrace();
            log.info("[VirtualHost] 创建绑定失败");
            return false;
        }
    }
    //删除绑定
    public boolean queueUnBind(String queueName,String exchangeName) {
        queueName = VirtualHostName + queueName;
        exchangeName = VirtualHostName + exchangeName;
        try {
            synchronized(exchangeLocker) {
                synchronized (queueLocker) {
                    //1.检查当前绑定是否存在
                    Binding binding = memoryDataManage.getBinding(queueName,exchangeName);
                    if(binding == null) {
                        throw new MqException("[VirtualHost] 当前绑定不存在");
                    }
                    //2.无论交换机和队列是否存在都进行删除，先删除硬盘
                    diskDataManage.deleteBinding(binding);
                    //3.再删内存
                    memoryDataManage.deleteBinding(binding);
                    log.info("[VirtualHost] 删除绑定成功");
                }
            }
            return true;
        }catch (Exception e) {
            e.printStackTrace();
            log.info("[VirtualHost] 删除绑定失败");
            return false;
        }
    }

    //发送消息到指定的交换机/队列中
    public boolean basicPublish(String exchangeName,String routingKey,BasicProperties basicProperties,byte[] body) {
        try {
            synchronized (exchangeLocker) {
                synchronized (queueLocker) {
                    exchangeName = VirtualHostName + exchangeName;
                    //1.查询当前交换机/队列是否存在
                    Exchange exchange = memoryDataManage.getExchange(exchangeName);
                    if(exchange == null) {
                        throw new MqException("[VirtualHost] 当前交换机不存在，发送消息失败");
                    }
                    //2.检查routingKey的合法性
                    if(!Router.checkRoutingKey(routingKey)) {
                        throw new MqException("[VirtualHost] routingKey不合法，发送消息失败");
                    }
                    //3.判断当前交换机的类型，不同类型转发方式不同
                    if(exchange.getType() == ExchangeType.DIRECT) {
                        //直接交换机，转发到以routingKey作为队列的名字，添加到对应队列中去
                        String queueName = VirtualHostName + routingKey;
                        MSGQueue queue = memoryDataManage.getQueue(queueName);
                        if(queue == null) {
                            throw new MqException("[VirtualHost] 队列不存在，发送消息失败");
                        }
                        //构造message对象
                        basicProperties.setRoutingKey(routingKey);
                        Message message = Message.createMessageWithId(basicProperties,body);
                        //发送到指定队列中
                        sendMessage(queueName,message);
                    }else {
                        //还有扇出(每一个队列发送信息)和主题交换机（routingKey和BindingKey对应上的队列才发送信息）
                        //使用这两种交换机的话，肯定需要遍历所有队列
                        ConcurrentHashMap<String,Binding> bindingsMap = memoryDataManage.getBindings(exchangeName);
                        for(Map.Entry<String,Binding> entry : bindingsMap.entrySet()) {
                            //(1)获取当前队列名称和binding
                            String queueName = entry.getKey();
                            Binding binding = entry.getValue();
                            //(2)判定当前队列是否存在，如果不存在，直接抬走下一位
                            MSGQueue queue = memoryDataManage.getQueue(queueName);
                            if(queue == null) {
                                log.info("[VirtualHost] 当前队列不存在");
                                continue;
                            }
                            basicProperties.setRoutingKey(routingKey);
                            //3.判断交换机类型是否为主题交换机
                            if(!Router.route(exchange.getType(),binding,basicProperties)) {
                                continue;
                            }
                            //4.队列已经存在，构造message对象
                            Message message = Message.createMessageWithId(basicProperties,body);

                            //5.添加信息
                            sendMessage(queueName,message);
                            log.info("[VirtualHost] 添加信息成功");
                            return true;
                        }
                    }
                    return false;
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
            log.info("[VirtualHost] 发送信息失败");
            return false;
        }
    }

    private void sendMessage(String queueName, Message message) throws IOException, InterruptedException {
        //1.判断队列是否存在
        MSGQueue queue = memoryDataManage.getQueue(queueName);
        if(queue == null) {
            throw new MqException("[VirtualHost] 当前要写的队列不存在");
        }
        //2.判断消息是否需要持久化
        if(message.getBasicProperties().getDeliveryMode() == 2) {
            diskDataManage.writeMessage(queue,message);
        }
        //3.写入内存
        memoryDataManage.sendMessage(queueName,message);
        consumerManager.putMassageQueue(queueName);
        consumerManager.notifyConsumer(queueName);
    }

    //订阅消息
    public boolean basicConsumer(String consumerTag, String queueName, boolean autoAck, Consumer consumer) {
        queueName = VirtualHostName + queueName;
        try {
            //构造消费管理者(触发扫描线程，后台运行)，并将消费者添加到对应的队列
            consumerManager.addConsumer(consumerTag,queueName,autoAck,consumer);
            log.info("[VirtualHost] 订阅消息成功");
            return true;
        }catch (Exception e) {
            e.printStackTrace();
            log.info("[VirtualHost] 订阅消息失败");
            return false;
        }
    }

    //消费者手动确认消息消费完成（其实就是对消息的删除）
    public boolean basicAck(String queueName,String messageId) {
        queueName = VirtualHostName + queueName;
        try {
            //1.获取到对应的队列和消息
            MSGQueue queue = memoryDataManage.getQueue(queueName);
            if(queue == null) {
                throw new MqException("[VirtualHost] 当前队列不存在 queueName: " + queueName);
            }
            Message message = memoryDataManage.getMessage(messageId);
            if(message == null) {
                throw new MqException("[VirtualHost] 当前消息不存在");
            }
            //2.进行硬盘和内存上数据的删除
            memoryDataManage.removeMessage(messageId);
            memoryDataManage.pollMessage(queueName);
            if(message.getBasicProperties().getDeliveryMode() == 2) {
                //表示消息已经持久化，需要删除
                diskDataManage.deleteMessage(queue,message);
            }
            memoryDataManage.removeMessageWaitAck(queueName,messageId);
            log.info("[VirtualHost] 手动确认消息消费完成成功");
            return true;
        }catch (Exception e) {
            e.printStackTrace();
            log.info("[VirtualHost] 手动确认消息消费完成失败");
            return false;
        }
    }

}
